package com.wfh.aigenerator.service.impl;

import cn.hutool.core.util.ZipUtil;
import com.wfh.aigenerator.common.ErrorCode;
import com.wfh.aigenerator.exception.BusinessException;
import com.wfh.aigenerator.service.ProjectDownloadService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileFilter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Set;

/**
 * @Title: ProjectDownloadServiceImpl
 * @Author wangfenghuan
 * @Package com.wfh.aigenerator.service.impl
 * @Date 2025/8/9 14:10
 * @description: 项目下载服务实现类，支持打包过滤指定目录和文件
 */
@Service
@Slf4j
public class ProjectDownloadServiceImpl implements ProjectDownloadService {

    /**
     * 需要过滤的文件和目录名称（完全匹配）
     */
    private static final Set<String> IGNORED_NAMES = Set.of(
            "node_modules",
            ".git",
            "dist",
            "build",
            ".DS_Store",
            ".env",
            "target",
            ".mvn",
            ".idea",
            ".vscode",
            "__pycache__",
            "venv",
            ".pytest_cache"
    );

    /**
     * 需要过滤的文件扩展名或后缀（用于 endsWith 判断）
     */
    private static final Set<String> IGNORED_SUFFIXES = Set.of(
            ".log",
            ".tmp",
            ".cache",
            ".swp",
            ".swo"
    );

    /**
     * 下载项目为压缩包（自动过滤指定目录和文件）
     *
     * @param projectPath      项目根目录路径
     * @param downloadFileName 下载的压缩包文件名（不含 .zip）
     * @param response         HTTP 响应对象
     * @return 实际返回的文件名（含 .zip）
     */
    @Override
    public String downloadProject(String projectPath, String downloadFileName, HttpServletResponse response) {
        // 基础参数校验
        if (projectPath == null || downloadFileName == null || response == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不能为空");
        }

        File projectDir = new File(projectPath);
        if (!projectDir.exists()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "项目路径不存在：" + projectPath);
        }
        if (!projectDir.isDirectory()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "项目路径不是一个目录：" + projectPath);
        }

        log.info("开始打包下载项目: {} -> {}.zip", projectPath, downloadFileName);

        // 设置 HTTP 响应头
        response.setStatus(HttpServletResponse.SC_OK);
        response.setContentType("application/zip");
        response.addHeader("Content-Disposition",
                "attachment; filename=\"" + downloadFileName + ".zip\"");

        // 创建文件过滤器：决定哪些文件/目录应被包含进压缩包
        FileFilter filter = fileToCheck -> isFileOrDirAllowed(projectDir, fileToCheck);

        try {
            // 使用 Hutool 将目录压缩并直接输出到响应流
            ZipUtil.zip(response.getOutputStream(), StandardCharsets.UTF_8, false, filter, projectDir);
            log.info("项目打包下载完成: {}.zip", downloadFileName);
        } catch (Exception e) {
            log.error("项目打包下载异常", e);
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "项目打包失败：" + e.getMessage());
        }

        return downloadFileName + ".zip";
    }

    /**
     * 判断某个文件或目录是否允许被打包
     *
     * @param rootDir     项目根目录
     * @param fileToCheck 当前检查的文件或目录
     * @return true 表示允许打包，false 表示跳过
     */
    private boolean isFileOrDirAllowed(File rootDir, File fileToCheck) {
        Path rootPath = rootDir.toPath();
        Path filePath = fileToCheck.toPath();

        // 确保 filePath 是 rootPath 的子路径（防止路径穿越）
        if (!filePath.startsWith(rootPath)) {
            return false;
        }

        // 计算相对于根目录的路径
        Path relativePath;
        try {
            relativePath = rootPath.relativize(filePath);
        } catch (IllegalArgumentException e) {
            log.warn("无法相对化路径: {} 相对于 {}", filePath, rootPath);
            return false;
        }

        // 检查相对路径中的每一级目录/文件名
        for (Path part : relativePath) {
            String partName = part.toString();

            // 完全匹配忽略名称（如 node_modules）
            if (IGNORED_NAMES.contains(partName)) {
                return false;
            }

            // 检查是否以忽略后缀结尾（如 .log, .tmp）
            if (IGNORED_SUFFIXES.stream().anyMatch(partName::endsWith)) {
                return false;
            }
        }

        return true;
    }
}
